Skip to content

fix(release): root-init at 600,000 PBKDF2 iterations; root custody as decided - #244

Merged
Madraka merged 2 commits into
mainfrom
claude/root-kdf-custody
Nov 15, 2026
Merged

Madraka merged 2 commits into
mainfrom
claude/root-kdf-custody

Conversation

@Madraka

@Madraka Madraka commented Nov 15, 2026

Copy link
Copy Markdown
Contributor

Two follow-ups to the trust ceremony (RFC-0046 §9). Scripts and docs only, so no changeset.

1. root-init KDF

  • Before: root-init wrote the root as PBES2 with PBKDF2-HMAC-SHA256 at 2,048 iterations. That is the default of Node's KeyObject.export({ cipher, passphrase }), and the API has no option for more.

  • Now: the new scripts/release-keys/pkcs8.ts writes the PBES2 EncryptedPrivateKeyInfo itself (RFC 8018, RFC 5958):

    • PBKDF2-HMAC-SHA256 at 600,000 iterations (ROOT_KDF_ITERATIONS, OWASP);
    • a 16-byte salt;
    • AES-256-CBC.

    This is the same form as openssl pkcs8 -topk8 -v2 aes-256-cbc -v2prf hmacWithSHA256 -iter 600000.

  • Reading roots: sign-keys is unchanged: createPrivateKey({ key, passphrase }) reads both the new and the old 2,048-iteration roots.

  • readPbes2 parses the PBES2/PBKDF2 parameters back out of the DER.

  • Tests (packages/cli/test/release-keys.test.ts):

    • The written file is exactly { iterations: 600000, saltBytes: 16, prf: "hmacWithSHA256", cipher: "aes-256-cbc" }.
    • It opens with the passphrase and yields the announced public key; a wrong passphrase fails.
    • Every file gets its own salt.
    • An old-form root (Node's default export, which readPbes2 reads as 2,048 iterations) still signs a keys.json that the consumer's verifyKeys accepts.
  • Checked by hand with OpenSSL 4.1: openssl asn1parse shows PBES2 / PBKDF2 / INTEGER 0927C0 / hmacWithSHA256 / aes-256-cbc, and openssl pkey -passin opens the file.

2. Custody text (owner decision, 2026-11-15)

  • docs/security/release-signing-keys.md, Root keys › Custody:
    • The encrypted root has two sealed copies:
      • one in the private repository cloudhal-dev/key-custody (owner only; no Actions, apps or deploy keys; no passphrase and no plaintext key in it);
      • one kept offline.
    • The passphrase is kept apart, in a password manager and on sealed paper.
    • The repository copy is the one documented exception.
    • A new bullet gives the by-hand re-encryption for roots made before this fix, how to check the result with asn1parse, and records that cloudhal-root-2026a was re-encrypted on 2026-11-15.
  • Custody of the release key: the general sentence now reads: "Apart from their environment secrets, no copy of the release key or the yank key, and no passphrase, is ever in a repository, a chat, an issue or a cloud service." It names the encrypted root in key-custody as the one exception.
  • Yank key:
    • The secret is its only copy: the plaintext key was deleted once the secret was set, and a lost key is rotated, not restored. The same holds for the yank-list deploy key.
    • Setup step 4 now says to delete the .pem instead of "move to offline storage".
    • Setup step 7 adds what to do if the deploy key's secret is lost.
  • RFC-0046 §9: the root custody line carries the same copies, the passphrase placement and the exception. It also states the KDF.

Tests run, one at a time

  • packages/cli: release-keys (22), release-workflows (14), release-trust-assets (4), yank-list-workflow (6), deploy-release (24).
  • packages/core-contracts: release-schemas (23).
  • packages/release-verify: package (16), trust.
  • Also clean: tsc in packages/cli (it type-checks the scripts it imports), biome, and pnpm size:check.

🤖 Generated with Claude Code

Madraka and others added 2 commits November 15, 2026 13:16
…s; custody as decided

root-init wrote the root as PBES2 with PBKDF2-HMAC-SHA256 at 2,048 iterations, Node's
KeyObject.export default, which has no option for more. scripts/release-keys/pkcs8.ts now writes
the PBES2 EncryptedPrivateKeyInfo itself (RFC 8018, RFC 5958): PBKDF2-HMAC-SHA256 at 600,000
iterations (OWASP), a 16-byte salt, AES-256-CBC, the same form as
`openssl pkcs8 -topk8 -v2 aes-256-cbc -v2prf hmacWithSHA256 -iter 600000`. Node and OpenSSL read
it with the passphrase; sign-keys still reads the older 2,048-iteration roots. readPbes2 parses
the parameters back from the DER, and a test asserts the written file's iteration count, prf and
cipher, and that an old-form root still signs a keys.json the consumer verifies.

Docs (owner decision, 2026-11-15): the root's encrypted file has two sealed copies, one in the
private cloudhal-dev/key-custody repository (owner only; no Actions, apps or deploy keys; no
passphrase, no plaintext key) and one offline; the passphrase is kept apart, in a password
manager and on sealed paper. That copy is the one documented exception to "no copy in a
repository or a cloud service", which now names the release key, the yank key and passphrases.
The plaintext yank key and the yank-list deploy key were deleted once set as release-yank
secrets; a lost one is rotated, not restored. The runbook gives the by-hand re-encryption for
roots made before this fix, and RFC-0046 §9's custody line carries the same exception.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Madraka <[email protected]>
…assword manager

The custody rule said no passphrase is ever in a cloud service, yet the root passphrase is kept
in a password manager, and most sync. Both the key doc and RFC-0046 §9 now say no passphrase is
ever in a repository, a chat, an issue, or a cloud service other than an end-to-end encrypted
password manager, and the root's passphrase is kept in such a password manager.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Madraka <[email protected]>
@Madraka
Madraka merged commit d9eaf52 into main Nov 15, 2026
9 checks passed
@Madraka
Madraka deleted the claude/root-kdf-custody branch November 15, 2026 10:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant